use super::{
    err, err_value, get_connect, json, ok, throw_err, Bytes, Connect, ECode, MapSv, RNull, Resp,
    S7Client, VBase, VMap, Value, VecV,
};
use bytes::{Buf, BufMut, BytesMut};
use s7::{
    constant::{Area, WL_BIT, WL_BYTE},
    error::Error,
};
use std::collections::HashMap;
use std::time::Instant;
use tracing::{info, instrument};

pub struct S7;

impl S7 {
    pub async fn do_cmd(&mut self, data: &MapSv, cmd: &str, client: &Connect) -> Resp {
        match cmd {
            "connect" => self.do_connect(data, client).await,
            "reconnect" => self.do_reconnect(data, client).await,
            "close" => self.do_close(data, client).await,
            "read" => self.do_read(data, client).await,
            "write" => self.do_write(data, client).await,
            _ => throw_err!(ECode::NoServe, "no s7 serve"),
        }
    }

    #[instrument(skip_all, name = "write")]
    async fn do_write(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let start = Instant::now();

        let id = data.ck_str("id")?;
        let cmd = data.ck_array("data")?;

        let con = get_connect(id, client)?;
        let mut plc = con.lock().await;

        let stream = match plc.get_s7() {
            Some(v) => v,
            None => {
                throw_err!(ECode::ReadVal, "connect error");
            }
        };

        for d in cmd.iter() {
            let d = d.ckobj()?;

            let tag = d.ck_str("k")?;
            let t = d.ck_str("t")?;
            let val = d.ck_array("v")?;
            let c = match d.k_i64("c") {
                Some(v) => v,
                None => 0,
            };
            let s = match d.k_i64("s") {
                Some(v) => v,
                None => 0,
            };
            let a = match d.k_bool("a") {
                Some(v) => v,
                None => false,
            };

            let mut buf = BytesMut::new();

            if t == "c" {
                let mut tmp = Vec::new();

                for it in val.iter() {
                    let v = it.ckarray()?;
                    tmp.push(v);
                }

                let len = if c > 0 { c } else { 1 };

                tmp.truncate(len as usize);

                for it in tmp.iter() {
                    self.write_custom(&mut buf, it)?;
                }
            } else {
                self.write_data(&mut buf, t, c, val, s, false, a)?;
            }

            let (addr, addr_num, addr_offset, bit) = self.parse_tag_s7(tag)?;

            let db = self.get_db(&addr);

            let mut buf = buf.to_vec();
            let len = buf.len();

            let word_len = if let Some(_) = bit { WL_BIT } else { WL_BYTE };

            let offset = if let Some(v) = bit {
                addr_offset * 8 + v
            } else {
                addr_offset
            };

            if let Err(e) = stream.write(db, addr_num, offset, len as i32, word_len, &mut buf) {
                match e {
                    Error::IOError { .. } => {
                        plc.close_s7();
                        throw_err!(ECode::SendVal, e);
                    }
                    _ => {
                        throw_err!(ECode::SendVal, e);
                    }
                }
            }
        } //for

        let duration = start.elapsed();
        info!("s7 write time: {:?} \t in: {:?}", duration, data);

        ok!()
    }

    #[instrument(skip_all, name = "read")]
    async fn do_read(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let start = Instant::now();

        let id = data.ck_str("id")?;
        let cmd = data.ck_array("data")?;

        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;

        let stream = match plc.get_s7() {
            Some(v) => v,
            None => {
                throw_err!(ECode::ReadVal, "connect error");
            }
        };

        let mut ret = HashMap::new();

        for d in cmd.iter() {
            let d = d.ckobj()?;

            let tag = d.ck_str("k")?;
            let label = d.ck_str("m")?;
            let t = d.ck_str("t")?;
            let c = match d.k_i64("c") {
                Some(v) => v,
                None => 0,
            };
            let s = match d.k_i64("s") {
                Some(v) => v,
                None => 0,
            };
            let a = match d.k_bool("a") {
                Some(v) => v,
                None => false,
            };
            let r = match d.k_bool("r") {
                Some(v) => v,
                None => false,
            };
            let le = match d.k_bool("le") {
                Some(v) => v,
                None => false,
            };
            let val = d.k_i64("v");

            let val = if t == "c" {
                match self.read_custom(stream, tag, d, c, le) {
                    Ok(v) => v,
                    Err(e) => {
                        if let Some(_) = e.k_str("e") {
                            plc.close_s7();
                            throw_err!(ECode::ReadVal, e);
                        }

                        return Err(e);
                    }
                }
            } else {
                match self.read_data(stream, t, c, tag, s, a, r, val, le) {
                    Ok(v) => v,
                    Err(e) => {
                        if let Some(_) = e.k_str("e") {
                            plc.close_s7();
                            throw_err!(ECode::ReadVal, e);
                        }

                        return Err(e);
                    }
                }
            };

            ret.insert(label, val);
        }

        let duration = start.elapsed();
        info!(
            "s7 read time: {:?} \t in: {:?} \t out: {:?}",
            duration, data, ret
        );

        ok!(ret)
    }

    async fn do_close(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let id = data.ck_str("id")?;
        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;

        plc.close_s7();

        ok!()
    }

    async fn do_reconnect(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let ip = data.ck_str("ip")?;
        let id = data.ck_str("id")?;
        let tp = data.ck_str("tp")?;
        let rank = match data.k_i64("rank") {
            Some(v) => v,
            None => 0,
        };
        let slot = match data.k_i64("slot") {
            Some(v) => v,
            None => 0,
        };

        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;
        plc.close_s7();

        plc.set_s7(ip, rank, slot, tp)?;

        ok!()
    }

    async fn do_connect(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let ip = data.ck_str("ip")?;
        let id = data.ck_str("id")?;
        let tp = data.ck_str("tp")?;
        let rank = match data.k_i64("rank") {
            Some(v) => v,
            None => 0,
        };
        let slot = match data.k_i64("slot") {
            Some(v) => v,
            None => 0,
        };

        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;

        plc.set_s7(ip, rank, slot, tp)?;

        ok!()
    }

    fn write_data(
        &self,
        buf: &mut BytesMut,
        t: &str,
        c: i64,
        value: &VecV,
        s: i64,
        align: bool,
        a: bool,
    ) -> RNull {
        let count = if c > 0 { c as usize } else { 1 };

        let mut char_val = Vec::new();
        if a && t == "char" && c > 0 && !value.is_empty() {
            let tmp = value[0].ckstr()?;
            for d in tmp.chars() {
                char_val.push(json!(d));
            }
        }

        let val = if char_val.is_empty() {
            value
        } else {
            &char_val
        };

        for (i, d) in val.iter().enumerate() {
            if i >= count {
                break;
            }

            match t {
                "bool" => {
                    let d = d.ckbool()?;
                    let d = if d { 0xFF } else { 0 };
                    buf.put_u8(d as u8);
                }
                "arraybool" => {
                    let d = d.ckarray()?;

                    let mut tmp = 0u8;

                    for (i, it) in d.iter().enumerate() {
                        if i > 7 {
                            break;
                        }

                        let it = it.ckbool()?;
                        if it {
                            tmp |= 1 << i;
                        }
                    }

                    buf.put_u8(tmp);
                }
                "char" => {
                    let mut d = d.ckstr()?.to_string().into_bytes();
                    d.resize(1, 0u8);
                    buf.put_slice(&d)
                }
                "byte" => {
                    let d = d.cki64()?;
                    buf.put_u8(d as u8)
                }
                "int16" => {
                    let d = d.cki64()?;
                    buf.put_i16(d as i16)
                }
                "int32" => {
                    let d = d.cki64()?;
                    buf.put_i32(d as i32)
                }
                "int64" => {
                    let d = d.cki64()?;
                    buf.put_i64(d)
                }
                "float" => {
                    let d = d.ckf64()?;
                    buf.put_f32(d as f32)
                }
                "string" => {
                    let mut d = d.ckstr()?.to_string().into_bytes();
                    d.truncate(s as usize);

                    let len = d.len();

                    let w_num = if s % 2 == 0 { s } else { s + 1 };

                    d.resize(w_num as usize, 0u8);

                    buf.put_u8(b'\n');
                    buf.put_u8(len as u8);
                    buf.put_slice(&d)
                }
                _ => {
                    throw_err!(ECode::SendVal, format!("value type error: {}", t));
                }
            }
        }

        if align && (count == 1 || count % 2 != 0) {
            match t {
                "bool" | "arraybool" | "char" | "byte" => {
                    buf.put_u8(0);
                }
                _ => {}
            }
        }

        Ok(())
    }

    fn write_custom(&self, buf: &mut BytesMut, data: &VecV) -> RNull {
        for d in data.iter() {
            let d = d.ckobj()?;

            let t = d.ck_str("t")?;
            let v = d.ck_array("v")?;
            let c = match d.k_i64("c") {
                Some(v) => v,
                None => 0,
            };
            let s = match d.k_i64("s") {
                Some(v) => v,
                None => 0,
            };
            let a = match d.k_bool("a") {
                Some(v) => v,
                None => false,
            };

            self.write_data(buf, t, c, v, s, true, a)?;
        }

        Ok(())
    }

    fn read_custom(&self, stream: &mut S7Client, tag: &str, d: &MapSv, c: i64, le: bool) -> Resp {
        let sub = d.ck_array("v")?;

        let mut len = 0;
        for sd in sub.iter() {
            let st = sd.ck_str("t")?;
            let sc = match sd.k_i64("c") {
                Some(v) => v,
                None => 0,
            };
            let ss = match sd.k_i64("s") {
                Some(v) => v,
                None => 0,
            };

            let val_len = self.get_len(st, ss);
            if val_len <= 0 {
                throw_err!(ECode::Format, "plc type error, len > 0");
            }

            let mut tmp = if sc > 0 { sc * val_len } else { val_len };
            if tmp % 2 != 0 {
                tmp += 1;
            }

            len += tmp;
        } //for

        if c > 0 {
            len *= c;
        }

        let (mut buf, bit) = self.read_tag(stream, tag, len)?;

        let mut ret = Vec::new();
        let count = if c > 0 { c } else { 1 };

        for _ in 0..count {
            let mut sub_val = HashMap::new();

            for sd in sub.iter() {
                let slable = sd.ck_str("m")?;
                let st = sd.ck_str("t")?;
                let sc = match sd.k_i64("c") {
                    Some(v) => v,
                    None => 0,
                };
                let ss = match sd.k_i64("s") {
                    Some(v) => v,
                    None => 0,
                };
                let sa = match sd.k_bool("a") {
                    Some(v) => v,
                    None => false,
                };
                let sr = match sd.k_bool("r") {
                    Some(v) => v,
                    None => false,
                };
                let val = sd.k_i64("v");

                let sv = self.read_buf(&mut buf, st, sc, ss, bit, sa, sr, val, le)?;
                sub_val.insert(slable, sv);
            } //for

            ret.push(json!(sub_val));
        } // for

        if ret.is_empty() {
            throw_err!(ECode::Format, "read plc type error, empty");
        }

        if c > 0 {
            Ok(json!(ret))
        } else {
            Ok(json!(ret[0]))
        }
    }

    fn read_data(
        &self,
        stream: &mut S7Client,
        t: &str,
        c: i64,
        tag: &str,
        s: i64,
        a: bool,
        r: bool,
        val: Option<i64>,
        le: bool,
    ) -> Resp {
        let len = self.get_len(t, s);
        if len <= 0 {
            throw_err!(ECode::Format, "plc type error, read len 0");
        }
        let len = if c > 0 { c * len } else { len };

        let (mut buf, bit) = self.read_tag(stream, tag, len)?;

        self.read_buf(&mut buf, t, c, s, bit, a, r, val, le)
    }

    fn read_tag(
        &self,
        stream: &mut S7Client,
        tag: &str,
        len: i64,
    ) -> Result<(Bytes, Option<i32>), Value> {
        let (addr, addr_num, addr_offset, bit) = self.parse_tag_s7(tag)?;

        let db = self.get_db(&addr);
        let mut buf = vec![0u8; len as usize];

        if let Err(e) = stream.read(db, addr_num, addr_offset, len as i32, WL_BYTE, &mut buf) {
            match e {
                Error::IOError { .. } => {
                    return Err(json!({"e": e.to_string()}));
                }
                _ => {
                    throw_err!(ECode::ReadVal, e);
                }
            }
        }

        Ok((Bytes::from(buf), bit))
    }

    fn eq_int(&self, val: i64, org: Option<i64>) -> Value {
        match org {
            Some(v) => {
                let t = if v == val { true } else { false };
                json!(t)
            }
            None => json!(val),
        }
    }

    /*
     * t: type
     * c: c > 0 array
     * s: t == "string", string's length
     * bit: t == "arraybool", offset bit
     * a: true: t == "char" && c > 0 char array change to string
     * r: true: string trim
     */
    fn read_buf(
        &self,
        buf: &mut Bytes,
        t: &str,
        c: i64,
        s: i64,
        bit: Option<i32>,
        a: bool,
        r: bool,
        val: Option<i64>,
        le: bool,
    ) -> Resp {
        let mut ret = Vec::new();
        let count = if c > 0 { c } else { 1 };

        for _ in 0..count {
            let v = match t {
                "bool" => {
                    let d = buf.get_u8();
                    let v = if let Some(b) = bit {
                        d & (1 << b) != 0
                    } else {
                        d > 0
                    };

                    json!(v)
                }
                "arraybool" => {
                    let mut tmp = Vec::new();
                    let d = buf.get_u8();

                    for i in 0..8 {
                        tmp.push(d & (1 << i) != 0);
                    }

                    json!(tmp)
                }
                "char" => {
                    let d = buf.get_u8();
                    let v = char::from_u32(d as u32);
                    json!(v)
                }
                "byte" => json!(buf.get_u8()),
                "int16" => self.eq_int(
                    if le { buf.get_i16_le() } else { buf.get_i16() } as i64,
                    val,
                ),
                "uint16" => self.eq_int(
                    if le { buf.get_u16_le() } else { buf.get_u16() } as i64,
                    val,
                ),
                "int32" => self.eq_int(
                    if le { buf.get_i32_le() } else { buf.get_i32() } as i64,
                    val,
                ),
                "uint32" => self.eq_int(
                    if le { buf.get_u32_le() } else { buf.get_u32() } as i64,
                    val,
                ),
                "int64" => self.eq_int(
                    if le { buf.get_i64_le() } else { buf.get_i64() } as i64,
                    val,
                ),
                "uint64" => self.eq_int(
                    if le { buf.get_u64_le() } else { buf.get_u64() } as i64,
                    val,
                ),
                "float" => json!(if le { buf.get_f32_le() } else { buf.get_f32() }),
                "double" => json!(if le { buf.get_f64_le() } else { buf.get_f64() }),
                "string" => {
                    if buf.remaining() >= 2 {
                        buf.advance(2);
                    }

                    let mut tmp = Vec::new();
                    for _ in 0..s {
                        tmp.push(buf.get_u8());
                    }

                    if s % 2 != 0 {
                        if buf.remaining() >= 1 {
                            buf.advance(1);
                        }
                    }

                    match String::from_utf8(tmp) {
                        Ok(d) => {
                            let d = d.trim_start_matches("#\u{0000}");
                            let mut d = d.trim_matches(char::is_control);
                            if r {
                                d = d.trim();
                            }

                            json!(d)
                        }
                        Err(e) => throw_err!(ECode::Format, e),
                    }
                }
                _ => throw_err!(ECode::Format, "read plc type error"),
            };

            ret.push(v);
        } //for

        if ret.is_empty() {
            throw_err!(ECode::Format, "read plc type error");
        }

        if count == 1 || count % 2 != 0 {
            match t {
                "bool" | "arraybool" | "char" | "byte" => {
                    if buf.remaining() >= 1 {
                        buf.advance(1);
                    }
                }
                _ => {}
            }
        }

        // char array changed to string
        if a && t == "char" && c > 0 {
            let mut tmp = String::new();

            for d in ret.iter() {
                if let Some(t) = d.kstr() {
                    tmp.push_str(t);
                }
            }

            let mut tmp = tmp.trim_matches(char::is_control);
            if r {
                tmp = tmp.trim();
            }

            return Ok(json!(tmp));
        }

        if c > 0 {
            Ok(json!(ret))
        } else {
            Ok(json!(ret[0]))
        }
    }

    fn parse_tag_s7(&self, tag: &str) -> Result<(String, i32, i32, Option<i32>), Value> {
        let tmp: Vec<&str> = tag.split('.').collect();
        if tmp.is_empty() {
            throw_err!(ECode::Format, "plc tag error");
        }

        let offset = if tmp.len() >= 2 {
            let t: i32 = match tmp[1].parse() {
                Ok(v) => v,
                Err(e) => throw_err!(ECode::Format, e),
            };
            t
        } else {
            0
        };

        let bit = if tmp.len() >= 3 {
            let t: i32 = match tmp[2].parse() {
                Ok(v) => v,
                Err(e) => throw_err!(ECode::Format, e),
            };

            Some(t)
        } else {
            None
        };

        let tmp = tmp[0];

        let addr = vec!["I", "Q", "M", "T", "C"];
        for d in addr.iter() {
            if tmp.starts_with(d) {
                let mut n: i32 = match tmp[1..].parse() {
                    Ok(v) => v,
                    Err(e) => throw_err!(ECode::Format, e),
                };

                if *d == "I" || *d == "Q" || *d == "M" {
                    n = 0;
                }

                return Ok((d.to_string(), n, offset, bit));
            }
        }

        if tmp.starts_with("DB") {
            let n: i32 = match tmp[2..].parse() {
                Ok(v) => v,
                Err(e) => throw_err!(ECode::Format, e),
            };

            return Ok(("DB".to_string(), n, offset, bit));
        }

        err!(ECode::Para, "plc tag format error")
    }

    fn get_len(&self, t: &str, s: i64) -> i64 {
        match t {
            "bool" | "byte" | "arraybool" | "char" => 1,
            "int16" | "uint16" => 2,
            "int32" | "uint32" => 4,
            "float" => 4,
            "string" => {
                let mut v = s + 2;
                if v % 2 != 0 {
                    v += 1;
                }

                v
            }
            _ => 0,
        }
    }

    fn get_db(&self, addr: &str) -> Area {
        match addr {
            "I" => Area::ProcessInput,
            "Q" => Area::ProcessOutput,
            "M" => Area::Merker,
            "DB" => Area::DataBausteine,
            "C" => Area::Counter,
            "T" => Area::Timer,
            _ => Area::Unknown,
        }
    }
}
